package org.reborn.model.bound

import org.reborn.math._

object BoundContains {
  
  def contains(container:Bound, containee:Bound):Boolean = {
	(container,containee) match{
		//Special case where one bound is an InfiniteBound
		case (ib:InfiniteBound,b:Bound) => true;		//Infinite bound contains everything
		case (b:Bound,ib:InfiniteBound) => false;		//Nothing contains infinite bound
		
		//Special case where one bound is an InfiniteFrustum
		case (b:Bound,f:InfiniteFrustum) => false;		//Nothing contains infinite frustum (except for Infinite Bound, but that case is handled
		
		//Container is InfiniteFrustum
		case (f:InfiniteFrustum,s:BoundingSphere) => infiniteFrustumContainsSphere(f,s)
		case (f:InfiniteFrustum,b:ClosedPolygonalBound) => infiniteFrustumContainsClosedPolygonalBound(f,b)
		
		//Container is BoundingSphere
		case (s1:BoundingSphere, s2:BoundingSphere) => sphereContainsSphere(s1,s2)
		case (s:BoundingSphere, b:AxisAlignedBoundingBox) => sphereContainsAABB(s,b)
		case (s:BoundingSphere, h:ConvexHull) => sphereContainsHull(s,h)
		
		//Container is AABB
		case (b1:AxisAlignedBoundingBox,b2:AxisAlignedBoundingBox) => aabbContainsAABB(b1,b2)
		case (b:AxisAlignedBoundingBox,s:BoundingSphere) => aabbContainsSphere(b,s)
		case (b:AxisAlignedBoundingBox, h:ConvexHull) => aabbContainsHull(b,h)
		
		//Container is ConvexHull
		case (h1:ConvexHull, h2:ConvexHull) => hullContainsHull(h1,h2)
		case (h:ConvexHull, s:BoundingSphere) => hullContainsSphere(h,s)
		case (h:ConvexHull, b:AxisAlignedBoundingBox) => hullContainsAabb(h,b)
		
		//If we don't have an implementation, throw an exception
		case _ => throw new UnsupportedOperationException("Contains test with container: "+container.getClass+" and containee:"+containee.getClass+" is not supported.");
	}
  }
	
  def infiniteFrustumContainsSphere(f:InfiniteFrustum,s:BoundingSphere):Boolean = {
	//If distance to all planes is negative then the sphere is contained
	f.planes.foreach{(plane)=>
	  val dist = plane.dist(s.position) + s.radius
	  if(dist > 0)
		return false
	}
	
	return true
  }
  
  def infiniteFrustumContainsClosedPolygonalBound(f:InfiniteFrustum,pb:ClosedPolygonalBound):Boolean = {
	//If all points on the box are on the negative side of all of the planes
	//then it is contained
	f.planes.forall{(plane)=>
	  pb.points.forall(plane.dist(_) <= 0f)
	}
  }
  
  def sphereContainsSphere(s1:BoundingSphere,s2:BoundingSphere):Boolean = {
	(s2.position - s1.position).magnitude + s2.radius <= s1.radius 
  }
  
  def sphereContainsAABB(s:BoundingSphere,b:AxisAlignedBoundingBox):Boolean = {
	val minDist = (s.position - b.min)
	val maxDist = (b.max - s.position)
	
	//Calculate the square of the furthest point on each axis, from the center of the sphere
	val maxDistSquared = for(i <- 0 until 3) yield {
	  val min = math.abs(minDist(i))
	  val max = math.abs(maxDist(i))
	  if(min < max)
	 	max * max
	  else
	 	min * min
	}
	
	//Sphere contains the box if the sum of squared components of the maximum distance point
	//is less than the radius of the sphere squared
	return maxDistSquared.reduceLeft(_+_) <= s.radius * s.radius 
  }
  
  def sphereContainsHull(s:BoundingSphere,h:ConvexHull):Boolean = {
	h.points.forall(s.containsPoint(_))
  }
  
  
  def aabbContainsAABB(b1:AxisAlignedBoundingBox,b2:AxisAlignedBoundingBox):Boolean = {
	b1.min(0) <= b2.min(0) &&
	b1.min(1) <= b2.min(1) &&
	b1.min(2) <= b2.min(2) &&
	b1.max(0) >= b2.max(0) &&
	b1.max(1) >= b2.max(1) &&
	b1.max(2) >= b2.max(2)
  }
  
  def aabbContainsSphere(b:AxisAlignedBoundingBox,s:BoundingSphere):Boolean = {
	b.min(0) <= s.position(0) - s.radius &&
	b.min(1) <= s.position(1) - s.radius &&
	b.min(2) <= s.position(2) - s.radius &&
	b.max(0) >= s.position(0) + s.radius &&
	b.max(1) >= s.position(1) + s.radius &&
	b.max(2) >= s.position(2) + s.radius
  }
  
  def aabbContainsHull(b:AxisAlignedBoundingBox,h:ConvexHull):Boolean = {
	//box contains hull if all points in the hull are within the box
	h.points.forall(b.containsPoint(_))
  }
  
  def hullContainsHull(h1:ConvexHull,h2:ConvexHull):Boolean = {
	//hull contains hull if all points are within bounding planes
	h2.points.forall(h1.containsPoint(_))
  }
  
  def hullContainsSphere(h:ConvexHull, s:BoundingSphere):Boolean = {
	//hull contains sphere if distance from center of sphere to all bounding
	//planes is greater than the sphere's radius
	h.polygons.forall((poly)=> poly.plane.dist(s.position) + s.radius <= 0f)
  }
  
  def hullContainsAabb(h:ConvexHull, b:AxisAlignedBoundingBox):Boolean = {
	//hull contains box if all box's points are within bounding planes
	b.points.forall(h.containsPoint(_))
  }

  
}